package com.smy.baseutil.dp;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

/**
 * 身份证号码的结构和表示形式<br>
 * 1、号码的结构<br>
 * 公民身份号码是特征组合码，由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，
 * 三位数字顺序码和一位数字校验码。<br>
 * 2、地址码<br>
 * 表示编码对象常住户口所在县(市、旗、区)的行政区划代码，按GB/T2260的规定执行。<br>
 * 3、出生日期码<br>
 * 表示编码对象出生的年、月、日，按GB/T7408的规定执行，年、月、日代码之间不用分隔符。<br>
 * 4、顺序码<br>
 * 表示在同一地址码所标识的区域范围内，对同年、同月、同日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配给女性。<br>
 * 5、校验码<br>
 * （1）十七位数字本体码加权求和公式<br>
 * S = Sum(Ai * Wi), i = 0, ... , 16 ，先对前17位数字的权求和<br>
 * Ai:表示第i位置上的身份证号码数字值<br>
 * Wi:表示第i位置上的加权因子<br>
 * Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2<br>
 * （2）计算模<br>
 * Y = mod(S, 11)<br>
 * （3）通过模得到对应的校验码<br>
 * Y: 0 1 2 3 4 5 6 7 8 9 10<br>
 * 校验码: 1 0 X 9 8 7 6 5 4 3 2<br>
 */
public final class IDCardUtil {

	/**
	 * 将15位身份证号转化为18位返回，非15位身份证号原值返回
	 * 
	 * @param identityCard
	 * @return
	 */
	public static String ic15To18(String identityCard) {
		String retId = "";
		String id17 = "";
		int sum = 0;
		int y = 0;
		// 定义数组存放加权因子（weight factor）
		int[] wf = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
		// 定义数组存放校验码（check code）
		String[] cc = { "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" };
		if (identityCard.length() != 15) {
			return identityCard;
		}
		// 加上两位年19
		id17 = identityCard.substring(0, 6) + "19" + identityCard.substring(6);
		// 十七位数字本体码加权求和
		for (int i = 0; i < 17; i++) {
			sum = sum + Integer.valueOf(id17.substring(i, i + 1)) * wf[i];
		}
		// 计算模
		y = sum % 11;
		// 通过模得到对应的校验码 cc[y]
		retId = id17 + cc[y];
		return retId;
	}

	/**
	 * 统一转成18位的身份证号码
	 * 
	 * @param identityCard
	 * @return
	 */
	public static String ic18(String identityCard) {
		Objects.requireNonNull(identityCard, "身份证号码不能为空。");
		if (identityCard.length() == 15) {
			return ic15To18(identityCard);
		}
		return identityCard;
	}

	/**
	 * 获取生日
	 * yyyyMMdd
	 * @param identityCard
	 * @return
	 */
	public static String getBrithday(String identityCard) {
		return ic18(identityCard).substring(6, 14);
	}
	
	/**
	 * 获取生日
	 * yyyy-MM-dd
	 * @param identityCard
	 * @return
	 */
	public static String getBrithdayFormat(String identityCard) {
		StringBuffer sb = new StringBuffer(10);
		String brithday = getBrithday(identityCard);
		sb.append(brithday.subSequence(0, 4)).append("-")
		  .append(brithday.substring(4, 6)).append("-")
		  .append(brithday.subSequence(6, 8));
		return sb.toString();
	}

	/**
	 * 获取性别 单数是男，双数是女
	 * 
	 * @param identityCard
	 * @return
	 */
	public static String getSex(String identityCard) {
		return ic18(identityCard).substring(16, 17);
	}
	
	/**
	 * 获取性别 单数是男，双数是女
	 * 	1-男，2-女，0-未知，9-未说明
	 * @param identityCard
	 * @return
	 */
	public static int getSex4Tk(String identityCard) {
		if (identityCard == null || identityCard.isEmpty()) {
			return 9;
		}
		for (int i = identityCard.length(); --i >= 0;) {
			if (!(Character.isDigit(identityCard.charAt(i)) || Character.isLetter('X'))) {
				return 9;
			}
		}
		Integer sex = Integer.valueOf(getSex(identityCard));
		int mod = sex % 2;
		if (mod == 0) {
			return 2;
		} else if (mod == 1) {
			return 1;
		}

		return 0;
	}

	/**
	 * 由出生日期获得年龄
	 * @param birthDay
	 * @return
	 * @throws Exception
	 */
	public static int getAge(Date birthDay) throws Exception {
		Calendar cal = Calendar.getInstance();
		if (cal.before(birthDay)) {
			throw new IllegalArgumentException("The birthDay is before Now.It's unbelievable!");
		}
		
		int yearNow = cal.get(Calendar.YEAR);
		int monthNow = cal.get(Calendar.MONTH);
		int dayOfMonthNow = cal.get(Calendar.DAY_OF_MONTH);
		cal.setTime(birthDay);

		int yearBirth = cal.get(Calendar.YEAR);
		int monthBirth = cal.get(Calendar.MONTH);
		int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);

		int age = yearNow - yearBirth;

		if (monthNow <= monthBirth) {
			if (monthNow == monthBirth) {
				if (dayOfMonthNow < dayOfMonthBirth)
					age--;
			} else {
				age--;
			}
		}
		
		return age;
	}
	
	/**
	 * 由出生日期获得年龄
	 * @param birthDay
	 * @return
	 * @throws Exception
	 */
	public static int getAge(String birthDay) {
		try {
			return getAge(new SimpleDateFormat("yyyyMMdd").parse(birthDay));
		} catch (Exception e) {
			return 0;
		}
	}
	
	/**
	 * 有身份证号码计算年龄
	 * @param idNo
	 * @return
	 */
	public static int getAgeByIdNo(String idNo) {
		return getAge(getBrithday(idNo));
	}

	/**
	 * 身份证脱敏
	 * @param idNo
	 * @return
	 */
	public static String hideIDCard(String idNo) {
	    if(idNo == null || idNo.isEmpty()){
	        return idNo;
	    }
	    
	    return idNo.replaceAll("(\\w{3})\\w+(\\w{4})","$1***********$2");
	}
	
	public static void main(String[] args) throws Exception {
		// 例子 511702800222130
		System.err.println(ic15To18("511702800222130"));
		// 结果 511702198002221308

		System.err.println(getBrithday("511702800222130"));

		System.err.println(getSex("511702800222130"));
		
		System.err.println(getSex4Tk("511702800222130"));
		
		System.err.println(getAge(getBrithday("511702800222130")));
	}

}
